from tkinter import *
from tkinter import filedialog, messagebox
import threading  # 新增：用于多线程处理
from bs4 import BeautifulSoup
from openpyxl import Workbook
from openpyxl.styles import PatternFill, Alignment, Border, Side, Font
from tqdm import tqdm  # 新增：导入 tqdm 用于进度条
import os  # 新增：用于文件操作

color_list = ['FFDD77', 'AADDFF', 'DDFFAA', 'FFAAFF', 'D3D3D3']  # 可根据需要扩展更多颜色


def get_rows(file_path):  # 修改：添加 file_path 参数
    # 读取HTML文件
    with open(
            file_path,  # 修改：使用传入的文件路径
            'r',
            encoding='utf-8') as file:
        html_content = file.read()

    # 使用BeautifulSoup解析HTML内容
    soup = BeautifulSoup(html_content, 'html.parser')

    # 存储结果的列表
    results = []

    # 查找所有的消息块
    msg_divs = soup.find_all('div', class_='msg')

    for msg_div in msg_divs:
        nickname_tag = msg_div.find('p', class_='nickname')
        content_tag = msg_div.find('p', class_='content')

        if nickname_tag and content_tag:
            # 提取昵称，去除时间部分
            nickname_text = nickname_tag.get_text()
            # 分割昵称和时间
            nickname_parts = nickname_text.split(" ")
            nickname = nickname_parts[0].strip()  # 只取昵称部分

            # 提取时间（如果存在）
            time_str = ''
            if len(nickname_parts) > 1:
                time_span = nickname_tag.find('span')
                if time_span:
                    time_str = time_span.get_text(strip=True)

            # 提取内容
            content = content_tag.get_text(strip=True)

            # 添加到结果列表中
            results.append((nickname, content, time_str))

    return results


class StatisticsApp:

    def __init__(self, root):
        self.root = root
        self.root.title("就餐群统计工具")
        self.root.geometry("400x300")
        self.is_running = False  # 新增：跟踪统计任务是否在运行
        self.thread = None  # 新增：保存线程对象以便后续操作

        # 文件路径变量
        self.file_path_var = StringVar()

        # 创建界面组件
        self.create_widgets()

    def create_widgets(self):
        # 添加一个标签显示欢迎信息
        welcome_label = Label(self.root,
                              text="欢迎使用就餐群统计工具！",
                              font=("Arial", 12))
        welcome_label.pack(pady=20)  # 将标签添加到窗口并设置垂直间距

        # 文件路径选择区域
        path_frame = Frame(self.root)
        path_frame.pack(pady=10, fill=X, padx=20)

        self.path_entry = Entry(path_frame,
                                textvariable=self.file_path_var,
                                font=("Arial", 10))
        self.path_entry.pack(side=LEFT, expand=True, fill=X)

        path_button = Button(path_frame,
                             text="选择文件",
                             command=self.select_file,
                             font=("Arial", 10))
        path_button.pack(side=LEFT, padx=(5, 0))

        # 添加一个按钮来触发统计功能
        self.start_button = Button(  # 修改：将开始按钮保存为实例变量
            self.root,
            text="开始统计",
            command=lambda: threading.Thread(target=self.run_tongji,
                                             args=(self, )).start(),
            font=("Arial", 12))
        self.start_button.pack(pady=10)  # 将按钮添加到窗口并设置垂直间距

        # 修改：重新设计退出按钮，使其能安全退出
        exit_button = Button(self.root,
                             text="退出",
                             command=self.safe_exit,
                             font=("Arial", 12))
        exit_button.pack(pady=10)  # 将退出按钮添加到窗口并设置垂直间距

    def select_file(self):
        # 文件选择对话框
        file_path = filedialog.askopenfilename(title="选择HTML文件",
                                               filetypes=[("HTML文件", "*.html"),
                                                          ("所有文件", "*.*")])
        if file_path:
            self.file_path_var.set(file_path)

    def run_tongji(self, app):
        """包装统计任务，添加按钮锁定和状态管理"""
        global stop_threads  # 声明使用全局变量
        if self.is_running:
            return

        self.is_running = True
        self.start_button.config(state=DISABLED)  # 锁定开始按钮
        stop_threads = False  # 重置停止标志

        try:
            self.thread = threading.Thread(target=self._run_tongji_with_check,
                                           args=(app, ))
            self.thread.start()
        except Exception as e:
            messagebox.showerror("错误", f"启动线程失败: {e}")
            self.is_running = False
            self.start_button.config(state=NORMAL)

    def _run_tongji_with_check(self, app):
        """在单独线程中执行 tongji 并周期性检查停止标志"""
        global stop_threads
        try:
            while not stop_threads:
                # 模拟持续执行任务，实际替换为 tongji(app)
                tongji(app)
                break  # 执行完成后退出循环
        finally:
            self.is_running = False
            self.start_button.config(state=NORMAL)
            stop_threads = False  # 重置标志

    def safe_exit(self):
        """安全退出程序"""
        global stop_threads
        stop_threads = True  # 设置停止标志

        if self.thread and self.thread.is_alive():
            self.thread.join(timeout=1)  # 尝试等待线程结束

        if messagebox.askokcancel("退出", "确定要退出程序吗？"):
            self.root.destroy()


def main():
    root = Tk()
    app = StatisticsApp(root)
    root.mainloop()


def tongji(app):  # 修改：添加 app 参数以访问 GUI 组件
    global stop_threads  # 使用全局变量来控制线程停止
    file_path = app.file_path_var.get()
    if not file_path:
        messagebox.showwarning("警告", "请先选择HTML文件！")
        return

    rows = get_rows(file_path)[2:]  # 修改：传递文件路径参数
    stats = {}
    for nickname, content, readable_time in rows:
        if nickname not in stats:
            stats[nickname] = {}
        if content not in stats[nickname]:
            stats[nickname][content] = []

        stats[nickname][content].append(readable_time)

    wb = Workbook()
    ws = wb.active
    ws.title = "统计数据"
    ws.append(['名称', '内容', '次数', '时间'])

    # 新增：记录昵称起始行
    nickname_start_row = 2  # 数据从第2行开始
    current_nickname = None
    current_color_index = 0  # 初始化 current_color_index
    nickname_ranges = []  # 存储所有待合并的昵称区间

    # 新增：记录内容起始行
    content_start_row = 2
    current_content = None
    content_ranges = []  # 存储所有待合并的内容区间

    # 新增：定义边框样式
    border_style = Border(left=Side(style='thin'),
                          right=Side(style='thin'),
                          top=Side(style='thin'),
                          bottom=Side(style='thin'))

    # 新增：为主循环添加进度条
    with tqdm(total=len(stats.items()), desc="总体进度", unit="item") as pbar_main:
        for nickname, contents in stats.items():
            with tqdm(total=len(contents.items()),
                      leave=False,
                      desc="子任务",
                      unit="sub") as pbar_sub:
                for content, times in contents.items():
                    if stop_threads:  # 检查全局停止标志
                        return
                    # 当检测到昵称变化时，记录上一个昵称的合并范围
                    if current_nickname != nickname:
                        if current_nickname is not None:
                            nickname_ranges.append(
                                (nickname_start_row, ws.max_row))
                        # 开始新的昵称区间
                        nickname_start_row = ws.max_row + 1
                        current_nickname = nickname

                    # 每个时间单独成行
                    for i, time_str in enumerate(times):
                        # 第一条数据保持原有内容
                        if i == 0:
                            ws.append(
                                [nickname, content,
                                 len(times), time_str])  # 修改：将计数列设为时间条目总数
                        else:
                            ws.append(['', content, '', time_str])

                        # 新增：为当前行的内容列（B、C、D列）添加外侧边框
                        for col in range(2, 5):  # 遍历 B、C、D 列
                            ws.cell(row=ws.max_row,
                                    column=col).border = border_style

                        # 检查内容变化并记录合并范围（移到这里以确保顺序）
                        if current_content != content:
                            if current_content is not None:
                                content_ranges.append(
                                    (content_start_row, ws.max_row - 1))
                            content_start_row = ws.max_row
                            current_content = content

                    # 更新内容计数为实际的时间数量
                    if len(times) > 0:
                        # 合并除首行外的昵称单元格
                        if len(times) > 1:
                            ws.merge_cells(start_row=ws.max_row - len(times) +
                                           1,
                                           start_column=1,
                                           end_row=ws.max_row,
                                           end_column=1)

                        # 新增：同步合并内容列和计数列，使用昵称列的范围
                        ws.merge_cells(start_row=ws.max_row - len(times) + 1,
                                       start_column=2,
                                       end_row=ws.max_row,
                                       end_column=2)
                        ws.merge_cells(start_row=ws.max_row - len(times) + 1,
                                       start_column=3,
                                       end_row=ws.max_row,
                                       end_column=3)

                    pbar_sub.update(1)  # 更新子进度条
            pbar_main.update(1)  # 更新主进度条

    # 添加最后一个昵称和内容的合并范围
    if current_nickname is not None:
        nickname_ranges.append((nickname_start_row, ws.max_row))
    if current_content is not None:
        content_ranges.append((content_start_row, ws.max_row))

    # 执行所有昵称列合并操作
    for start_row, end_row in nickname_ranges:
        if start_row <= end_row:  # 确保合法的合并范围
            ws.merge_cells(start_row=start_row,
                           start_column=1,
                           end_row=end_row,
                           end_column=1)

            # 新增：为合并的昵称区域填充背景色
            fill = PatternFill(
                start_color=color_list[current_color_index % len(color_list)],
                end_color=color_list[current_color_index % len(color_list)],
                fill_type="solid")
            # 新增：设置字体大小为20
            nickname_font = Font(size=20)
            for row in ws.iter_rows(min_row=start_row,
                                    max_row=end_row,
                                    min_col=1,
                                    max_col=4):
                for cell in row:
                    cell.fill = fill
                    if cell.column == 1:  # 名字列
                        cell.font = nickname_font
                        # 修改垂直对齐方式为靠上
                        cell.alignment = Alignment(vertical='top',
                                                   horizontal='center')
            current_color_index += 1  # 切换到下一个颜色

    # 新增：设置所有内容垂直居中显示
    for row in ws.iter_rows():
        for cell in row:
            cell.alignment = Alignment(vertical='center')

    # 新增：自动调整列宽
    for col in ws.columns:
        max_length = 0
        column = col[0].column_letter  # 获取列的字母标识（如A、B等）

        for cell in col:
            try:
                if len(str(cell.value)) > max_length:
                    max_length = len(str(cell.value))
            except:
                pass

        adjusted_width = (max_length + 2)  # 添加额外的宽度以确保留有空间
        ws.column_dimensions[column].width = adjusted_width

    # 新增：设置自动换行和行高
    for row in ws.iter_rows(min_col=4, max_col=4):  # 只遍历D列
        for cell in row:
            cell.alignment = Alignment(wrap_text=True)

    # 修复：正确获取单元格内容并计算行高
    for row in ws.iter_rows(min_row=2):  # 从第2行开始遍历
        times_str = row[3].value  # 获取D列的时间列表
        num_times = len(times_str.split('\n')) if times_str else 0
        ws.row_dimensions[row[0].row].height = 15 * num_times  # 设置行高
    print("保存文件中...")
    if not os.path.exists("./result"):
        os.makedirs("./result")
    # 保存 Excel 文件
    wb.save(
        f"./result/统计数据 {rows[0][-1].replace(':', '')} {rows[-1][-1].replace(':', '')}.xlsx"
    )
    print(
        f"统计数据已导出到 统计数据 {rows[0][-1].replace(':', '')} {rows[-1][-1].replace(':', '')}.xlsx"
    )

    # 新增：切换回主线程并提示用户是否打开结果文件夹
    app.root.after(
        0, lambda: (messagebox.showinfo("统计完成", "统计数据已成功导出到Excel文件！"),
                    os.startfile(r".\result")
                    if messagebox.askyesno("打开文件夹", "是否现在打开成果文件夹？") else None))


if __name__ == '__main__':
    main()

# 新增：定义一个全局变量用于控制线程停止
stop_threads = True  # 默认设置为 True，表示线程可以停止
